//
// Created by Martin on 2022/10/1.
//

#include <boost/static_assert.hpp>
#include <boost/mpl/apply.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/type_traits.hpp>
#include <iostream>
#include <iomanip>
#include <bitset>
#include <type_traits>

using namespace std;
using namespace boost;

using mpl::placeholders::_1;
using mpl::placeholders::_2;

// primary twice
template<class F, class X>
struct twice0
{
    typedef typename F::template apply<X>::type once;     // f(x)
    typedef typename F::template apply<once>::type type;  // f(f(x))
};

// improve twice by metafunction forwarding
// 要求参数F是元函数类, 内嵌public访问的apply元函数
template<class F, class X>
struct twice1
        : F::template apply<typename F::template apply<X>::type> // 元函数转发
{};

// improve twice1 by resolving the pattern to one metafunction
// 要求UnaryMetaFunctionClass是一元元函数类, 内嵌apply元函数
template<class UnaryMetaFunctionClass, class Arg>
struct apply1
        : UnaryMetaFunctionClass::template apply<Arg>
{};
template<class F, class X>
struct twice2
        : apply1<F, typename apply1<F, X>::type>
{};

// improve twice2 by using mpl::lambda
// mpl::lambda将参数F转换为元函数类
// 要求F必须为元函数类或占位符表达式
template<class F, class X>
struct twice3
        : apply1<
                typename mpl::lambda<F>::type,
                typename apply1<
                        typename mpl::lambda<F>::type,
                        X
                >::type
        >
{
};

// improve twice3 by using mpl::apply
// mpl::apply 用其余参数去调用第一个参数的元函数, 要求第一个参数必须是lambda表达式, 返回元函数
template<class F, class X>
struct twice4
        : mpl::apply<F, typename mpl::apply<F, X>::type>
{
};


struct add_pointer_f
{
    template<class T>
    struct apply
    {
        typedef typename boost::add_pointer<T>::type type;
    };
};

int main()
{
    BOOST_STATIC_ASSERT((
            boost::is_same<
                    twice0<add_pointer_f, int>::type,
                    int**
            >::value
    ));

    BOOST_STATIC_ASSERT((
            boost::is_same<
                    twice1<add_pointer_f, int>::type,
                    int**
            >::value
    ));

    BOOST_STATIC_ASSERT((
            boost::is_same<
                    twice2<add_pointer_f, int>::type,
                    int**
            >::value
    ));

    BOOST_STATIC_ASSERT((
            boost::is_same<
                    twice3<add_pointer_f, int>::type,
                    int**
            >::value
    ));

    BOOST_STATIC_ASSERT((
            boost::is_same<
                    twice4<add_pointer_f, int>::type,
                    int**
            >::value
    ));

    cout << "valid ok" << endl;
    return 0;
}